package com.nulldev.util.gui.swingAPI;

import java.awt.Adjustable;
import java.awt.Component;
import java.awt.Container;
import java.awt.DisplayMode;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.RenderingHints;
import java.awt.Window;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.font.FontRenderContext;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
import java.awt.peer.ComponentPeer;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

import javax.swing.JComponent;
import javax.swing.JDesktopPane;
import javax.swing.JFrame;
import javax.swing.JInternalFrame;
import javax.swing.JMenu;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;

import com.nulldev.util.JVM.JVM;
import com.nulldev.util.JVM.reflections.Reflections;
import com.nulldev.util.VariableAPI.ArrayUtils;
import com.nulldev.util.VariableAPI.StringsUtil;
import com.nulldev.util.data.Variables;
import com.nulldev.util.data.Arrays.arrays.FastLists;
import com.nulldev.util.internal.backport.concurrency9.Lists;
import com.nulldev.util.osAPI.OS;
import com.nulldev.util.osAPI.OS.OperatingSystem;

public class Swing {

	public static void center(final Window frame) {
		frame.setLocation((GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDisplayMode().getWidth() - frame.getWidth()) / 2,
				(GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDisplayMode().getHeight() - frame.getHeight()) / 2);
	}

	public static void enableJSO() {
		if (!isX11Safe() && (OS.get() == OperatingSystem.Linux || OS.get() == OperatingSystem.BSD)) {
			enableHeadless();
		}
		/* Java2D/Marlin */
		System.setProperty("sun.java2d.renderer.clip.runtime.enable", "true");
		System.setProperty("sun.java2d.renderer.profile", "speed");
		System.setProperty("sun.java2d.renderer.usePathSimplifier", "true");
		/* Swing */
		System.setProperty("swing.bufferPerWindow", "true");
		System.setProperty("swing.useflipBufferStrategy", "true");
		/* Platform Specific */
		if (JVM.version() >= 16f)
			System.setProperty("sun.java2d.offscreenSharing", "true");
		if (OS.get() == OperatingSystem.Windows) {
			System.setProperty("sun.java2d.noddraw", "false");
			System.setProperty("sun.java2d.transaccel", "true");
			System.setProperty("sun.java2d.d3d", "true");
			System.setProperty("sun.java2d.ddforcevram", "true");
		} else {
			System.setProperty("sun.java2d.pmoffscreen", "true");
			System.setProperty("sun.java2d.opengl", "true");
			System.setProperty("sun.java2d.xrender", "true");
		}
	}

	public static void enableAA() {
		System.setProperty("awt.useSystemAAFontSettings", "on");
		System.setProperty("swing.aatext", "true");
	}

	public static void enableHeadless() {
		System.setProperty("java.awt.headless", "true");
	}

	private static boolean ran, x11Safe;

	public static boolean isX11Safe() {
		if (ran) {
			return x11Safe;
		} else {
			if (OS.get() != OperatingSystem.Linux && OS.get() != OperatingSystem.BSD) {
				ran = true;
				return x11Safe = true;
			} else if (isWaylandOrXWl()) {
				System.out.println("[WARNING] You seem to be using Wayland or XWayland.");
				System.out.println("This is completely unsupported as Wayland is a shitheap not addressing Xorg's flaws in anyway what-so-ever.");
				System.out.println("You are on your own, good luck.");
				ran = true;
				return x11Safe = true;
			} else {
				try {
					final JFrame test_frame = new JFrame("nullUtil 3.0: X11 Compat Test");
					test_frame.setVisible(true);
					Thread.sleep(33L);
					test_frame.setVisible(false);
					test_frame.dispose();
					return x11Safe = true;
				} catch (Throwable e) {
					ran = true;
					return x11Safe = false;
				} finally {
					ran = true;
				}
			}
		}
	}

	private static boolean isWaylandOrXWl() {
		/**
		 * RANT:
		 * 
		 * Wayland is one of biggest shitheaps that I have ever seen, literally
		 * """""""""""security"""""""""""" over feature parity.
		 * 
		 * UNIX users are better off sticking to Xorg, Xenocara or pray to whatever
		 * higher power you believe in that alternate display servers like Arcan reach
		 * popularity and daily use.
		 * 
		 * I literally cannot do anything fundamental with many sub-projects because
		 * apparently reading the cursor position outside of the window's bounds is
		 * supposedly Hitler-incarnate.
		 */
		if (OS.get() != OperatingSystem.Linux && OS.get() != OperatingSystem.BSD) {
			return false;
		} else {
			final String sessionType = System.getenv("XDG_SESSION_TYPE");
			final String wlDisplay = System.getenv("WAYLAND_DISPLAY");
			return (sessionType != null && sessionType.equalsIgnoreCase("wayland")) || !StringsUtil.isBlank(wlDisplay);
		}
	}

	private static final Object charsBufferLock = new Object();
	private static char[] charsBuffer = new char[100];

	public static int stringWidth(final FontMetrics fm, final String string) {
		return stringWidth(null, fm, string);
	}

	public static int stringWidth(JComponent arg, FontMetrics arg0, String arg1) {
		if (arg1 != null && !arg1.equals("")) {
			boolean arg2 = arg != null && arg.getClientProperty(TextAttribute.NUMERIC_SHAPING) != null;
			if (arg2) {
				@SuppressWarnings("unused")
				Object arg3 = charsBufferLock;
				synchronized (charsBufferLock) {
					int arg4 = syncCharsBuffer(arg1);
					arg2 = isComplexLayout(charsBuffer, 0, arg4);
				}
			}

			if (arg2) {
				TextLayout arg7 = createTextLayout(arg, arg1, arg0.getFont(), arg0.getFontRenderContext());
				return (int) arg7.getAdvance();
			} else {
				return arg0.stringWidth(arg1);
			}
		} else {
			return 0;
		}
	}

	public static final boolean isComplexLayout(char[] arg, int arg0, int arg1) {
		return isComplexText(arg, arg0, arg1);
	}

	public static boolean isComplexText(char[] arg, int arg0, int arg1) {
		for (int arg2 = arg0; arg2 < arg1; ++arg2) {
			if (arg[arg2] >= 768 && isNonSimpleChar(arg[arg2])) {
				return true;
			}
		}

		return false;
	}

	public static boolean isNonSimpleChar(char arg) {
		return isComplexCharCode(arg) || arg >= '?' && arg <= '?';
	}

	public static boolean isComplexCharCode(int arg) {
		return arg >= 768 && arg <= 8303 ? (arg <= 879 ? true
				: (arg < 1424 ? false
						: (arg <= 1791 ? true
								: (arg < 2304 ? false
										: (arg <= 3711 ? true
												: (arg < 3840 ? false
														: (arg <= 4095 ? true
																: (arg < 4352 ? false
																		: (arg < 4607 ? true
																				: (arg < 6016 ? false
																						: (arg <= 6143 ? true
																								: (arg < 8204 ? false
																										: (arg <= 8205 ? true
																												: (arg >= 8234 && arg <= 8238 ? true
																														: arg >= 8298
																																&& arg <= 8303))))))))))))))
				: false;
	}

	@SuppressWarnings(
		{ "rawtypes", "unchecked" })
	private static TextLayout createTextLayout(JComponent arg, String arg0, Font arg1, FontRenderContext arg2) {
		Object arg3 = arg == null ? null : arg.getClientProperty(TextAttribute.NUMERIC_SHAPING);
		if (arg3 == null) {
			return new TextLayout(arg0, arg1, arg2);
		} else {
			HashMap arg4 = new HashMap();
			arg4.put(TextAttribute.FONT, arg1);
			arg4.put(TextAttribute.NUMERIC_SHAPING, arg3);
			return new TextLayout(arg0, arg4, arg2);
		}
	}

	private static int syncCharsBuffer(String arg) {
		int arg0 = arg.length();
		if (charsBuffer != null && charsBuffer.length >= arg0) {
			arg.getChars(0, arg0, charsBuffer, 0);
		} else {
			charsBuffer = arg.toCharArray();
		}

		return arg0;
	}

	public static long refreshRate() {
		try {
			final GraphicsEnvironment gge = GraphicsEnvironment.getLocalGraphicsEnvironment();
			final GraphicsDevice[] gs = gge.getScreenDevices();
			for (int i = 0; i < gs.length; i++) {
				final GraphicsDevice ge = gs[i];
				if (!gge.getDefaultScreenDevice().equals(ge))
					continue;
				final DisplayMode dm = ge.getDisplayMode();
				final int refreshRate = dm.getRefreshRate();
				if (refreshRate == DisplayMode.REFRESH_RATE_UNKNOWN)
					return 60L;
				else
					return refreshRate;
			}
			return 60L;
		} catch (Exception e) {
			return 60L;
		}
	}

	public static boolean isUsingHWAccel() {
		return System.getProperty("sun.java2d.opengl", "").equalsIgnoreCase("true") || System.getProperty("sun.java2d.d3d", "").equalsIgnoreCase("true");
	}

	public static List<Component> getAllComponentsInternal(final Container c) {
		if (c == null)
			return Lists.of();
		final Component[] comps = c.getComponents();
		final List<Component> compList = FastLists.list(64);
		for (Component comp : comps) {
			if (comp instanceof JDesktopPane || comp instanceof JInternalFrame) {
			} else if (comp instanceof Container) {
				compList.add(comp);
				compList.addAll(getAllComponents((Container) comp));
			} else if (comp instanceof JMenu) {
				compList.add(comp);
				final JMenu menu = (JMenu) comp;
				compList.addAll(ArrayUtils.toArrayList(menu.getMenuComponents()));
			} else {
				compList.add(comp);
			}
		}
		return compList;
	}

	@SuppressWarnings("unchecked")
	public static List<Component> getAllComponents(final Container c) {
		final List<Component> out = new CopyOnWriteArrayList<Component>();
		final LinkedList<Component> items = new LinkedList<Component>();
		for (final Component cmp : getAllComponentsInternal(c)) {
			items.add(cmp);
		}
		while (!items.isEmpty()) {
			for (final Component cx : (List<Component>) items.clone()) {
				if (cx instanceof JDesktopPane || cx instanceof JInternalFrame) {
				} else if (cx instanceof JMenu) {
					items.addAll(ArrayUtils.toArrayList(((JMenu) cx).getMenuComponents()));
				} else {
					out.add(items.removeFirst());
				}
			}
		}
		return out;
	}

	public static void scroll(final JScrollPane scrollPane, final ScrollDirection direction) {
		final JScrollBar verticalBar = Variables.requireNonNullObject(scrollPane, "scrollPane").getVerticalScrollBar();
		// If we want to scroll to the top set this value to the minimum, else to the
		// maximum
		final int topOrBottom = direction.equals(ScrollDirection.UP) ? verticalBar.getMinimum() : verticalBar.getMaximum();
		final AdjustmentListener scroller = new AdjustmentListener() {
			@Override
			public void adjustmentValueChanged(final AdjustmentEvent e) {
				final Adjustable adjustable = e.getAdjustable();
				adjustable.setValue(topOrBottom);
				// We have to remove the listener since otherwise the user would be unable to
				// scroll afterwards
				verticalBar.removeAdjustmentListener(this);
			}
		};
		verticalBar.addAdjustmentListener(scroller);
	}

	public static enum ScrollDirection {
		UP,
		DOWN
	}

	public static ComponentPeer getPeer(final Component comp) {
		if (JVM.version() <= 1.8f) {
			return (ComponentPeer) Reflections.encapsulate(comp).invoke("getPeer");
		} else {
			try {
				final Field peerField = Component.class.getDeclaredField("peer");
				if (!peerField.isAccessible())
					peerField.setAccessible(true);
				return (ComponentPeer) peerField.get(comp);
			} catch (Exception ex) {
				throw new RuntimeException(ex);
			}
		}
	}

	private static final boolean __is_aa_workaround_required;
	static {
		/**
		 * Infinality Remix breaks text anti-aliasing on Swing.
		 */
		final String infinality_wa = System.getenv().get("INFINALITY_FT_USE_VARIOUS_TWEAKS");
		__is_aa_workaround_required = (infinality_wa != null);
	}

	public static Object autoAAHint() {
		if (__is_aa_workaround_required) {
			return RenderingHints.VALUE_ANTIALIAS_OFF;
		} else {
			return RenderingHints.VALUE_ANTIALIAS_ON;
		}
	}
}
